#ifndef SM2_SM2_H
#define SM2_SM2_H

#include "Sm3.h"
#include <stdint.h>
#include <string.h>

// definition of large numbers
typedef uint64_t uint512_t[8];
typedef uint64_t uint256_t[4];

// definition of point
typedef struct {
    uint512_t X;
    uint512_t Y;
    uint512_t Z;
} u512_point_t; // Jacobian

typedef struct {
    uint256_t X;
    uint256_t Y;
} u256_point_t; // Affine

// convert affine coordinates to Jacobian coordinates based on sign
static void u256_point_to_u512_point(int negative, u512_point_t *u512_point, const u256_point_t *u256_point);

static void u256_to_u512(uint512_t *u512, const uint256_t *u256) {
    int i;
    for (i = 0; i < 4; i++) {
        (*u512)[2 * i] = (*u256)[i] & 0xffffffff;
        (*u512)[2 * i + 1] = (*u256)[i] >> 32;
    }
}

static void u512_to_u256(uint256_t *u256, const uint512_t *u512) {
    int i;
    for (i = 0; i < 4; i++) {
        (*u256)[i] = (*u512)[2 * i] | (*u512)[2 * i + 1] << 32;
    }
}

// SM2 P
extern const uint512_t GM_BN_P;
// SM2 N
extern const uint512_t GM_BN_N;
// SM2 mont G_PTR
extern const u512_point_t *GM_MONT_G_PTR;
// mont, mode p 1
extern const uint512_t GM_BN_MONT_PONE;
// mont, SM2 A
extern const uint512_t GM_BN_MONT_A;
// mont, SM2 B
extern const uint512_t GM_BN_MONT_B;

#define uint512_copy(r, a) memcpy((r), (a), sizeof(uint512_t))

int sm2_hex2bin(const char *in, int in_len, uint8_t *out);

void uint512_to_bytes(const uint512_t a, uint8_t out[32]);

void uint512_from_bytes(uint512_t r, const uint8_t in[32]);

void uint512_to_hex(const uint512_t a, char hex[64]);

int uint512_from_hex(uint512_t r, const char hex[64]);

// return a == b ? 0 : (a > b ? 1 : -1)
int uint512_cmp(const uint512_t a, const uint512_t b);

// r = (a + b)(mod m)
void uint512_add(uint512_t r, const uint512_t a, const uint512_t b, const uint512_t m);

// r = (a - b)(mod m)
void uint512_sub(uint512_t r, const uint512_t a, const uint512_t b, const uint512_t m);

// r = mont(a)(mod m)
void uint512_to_mont(uint512_t r, const uint512_t a, const uint512_t m);

// r = uint512_t(a)(mod m)
void uint512_from_mont(uint512_t r, const uint512_t a, const uint512_t m);

// r = (a * b)(mod m)
void uint512_mont_mul(uint512_t r, const uint512_t a, const uint512_t b, const uint512_t m);

// r = (a * a)(mod m)
void uint512_sqr(uint512_t r, const uint512_t a, const uint512_t m);

// r = (a ^ b)(mod m)
void uint512_exp(uint512_t r, const uint512_t a, const uint512_t b, const uint512_t m);

// r = (a ^ -1)(mod m)
void uint512_inv(uint512_t r, const uint512_t a, const uint512_t m);

// r = 0
void uint512_set_zero(uint512_t r);

// r = mont(1)
void uint512_set_mont_one(uint512_t r);

// return r == mont(1)
int uint512_is_mont_one(const uint512_t r);

// return r == 0
int uint512_is_zero(const uint512_t r);

// r = infinity_point
#define uint512_point_set_infinity(r) uint512_point_init(r)

// r = p
#define uint512_point_copy(r, p) memcpy((r), (p), sizeof(u512_point_t))

// r = infinity_point
void uint512_point_init(u512_point_t *r);

// r = jacobian_point(affine_x, affine_y)
void uint512_point_set_xy(u512_point_t *r, const uint512_t x, const uint512_t y);

// (mont_x, mont_y) = jacobian_point(p)
void u512_point_get_xy_mont(const u512_point_t *p, uint512_t x, uint512_t y);

// (affine_x, affine_y) = jacobian_point(p)
void uint512_point_get_xy(const u512_point_t *p, uint512_t x, uint512_t y);

// return p == infinity_point
int u512_point_is_at_infinity(const u512_point_t *p);

void u512_point_from_bytes(u512_point_t *r, const uint8_t in[64]);

// p = decode(in(33B/65B))
void u512_point_decode(u512_point_t *p, const uint8_t *in);

// r = 2 * p
void u512_point_double(u512_point_t *r, const u512_point_t *p);

// r = a + b
void u512_point_add(u512_point_t *r, const u512_point_t *a, const u512_point_t *b);

// r = k * p
void u512_point_mul(u512_point_t *r, const uint512_t* k, const u512_point_t *p);

// r = k * G
void u512_point_mul_G(u512_point_t *r, const uint512_t *k);


int sm2_do_sign(const uint512_t key, const uint512_t digest, unsigned char sig[64]);

int sm2_do_verify(const u512_point_t *key, const uint512_t digest, const unsigned char sig[64]);

typedef struct {
    uint512_t private_key;
    u512_point_t public_key;

    unsigned char x2y2[64];

    gm_sm3_context sm3_ctx;

    unsigned char buf[32];
    unsigned int cur_buf_len;
    unsigned int ct;
    unsigned int state;
} sm2_context;

int recover_private_key(sm2_context *ctx, const unsigned char *key, unsigned int kLen);


int sm2_sign_init(sm2_context *ctx, const unsigned char *key, unsigned int kLen,
                     const unsigned char *id_bytes, unsigned int idLen, int forSign);


void sm2_sign_update(sm2_context *ctx, const unsigned char *input, unsigned int iLen);


int sm2_sign_generate(sm2_context *ctx, unsigned char sig[64]);


int sm2_sign_verify(sm2_context *ctx, unsigned char sig[64]);


void sm2_compute_z_digest(const unsigned char *id_bytes, unsigned int idLen, const u512_point_t *pub_key,
                             unsigned char output[32]);


int sm2_crypt_init(sm2_context * ctx, const unsigned char * key, unsigned int kLen, int forEncryption, unsigned char c1[64]);

int sm2_crypt_init_ex(sm2_context * ctx, const unsigned char * key, unsigned int kLen,
                               int forEncryption, unsigned char c1[64], const uint512_t *test_key);

int sm2_crypt_update(sm2_context * ctx, const unsigned char * input, unsigned int iLen, unsigned char * output);

int sm2_crypt_done(sm2_context * ctx, unsigned char * output, unsigned char c3[32]);

#endif//SM2_SM2_H
